package com.blockchainenergy.blockChain;

import com.alibaba.fastjson.JSON;
import com.blockchainenergy.common.CarbonTools;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @Description 存储操作链的类
 **/
public class BlockChain {
    // 存储区块链
    private List<Block> chain;
    // 当前交易
    private Carbon currentCarbon;
    //     单例模式类
    private static volatile BlockChain blockChain = null;
    //  决定了挖矿的困难程度
    private static String difficulty = "0000";

    // 用于存储网络中其他节点的集合
    private Set<String> nodes;

    public Set<String> getNodes() {
        return nodes;
    }

    // 节点密钥 长度必须是16的倍数
    private static final String KEY = "0_bet_C!0_bet_C!";

    private BlockChain() throws Exception {
        // 用于存储网络中其他节点的集合
        nodes = new HashSet<String>();

        // 初始化区块链以及当前的交易信息列表
        chain = new ArrayList<>();
        // 创建创世区块
        newBlock(100L, "0");
    }

    // 创建单例对象
    public static BlockChain getInstance() throws Exception {
        if (blockChain == null) {
            synchronized (BlockChain.class) {
                if (blockChain == null) {
                    blockChain = new BlockChain();
                }
            }
        }
        return blockChain;
    }

    public List<Block> getChain() {
        return chain;
    }

    public void setChain(List<Block> chain) {
        this.chain = chain;
    }

    /**
     * @return 得到区块链中的最后一个区块
     */
    public Block lastBlock() {
        return getChain().get(getChain().size() - 1);
    }

    public String getDifficulty() {
        return difficulty;
    }

    public String getLastBlockHash() {
        return (String) hash(lastBlock());
    }

    /**
     * 新建区块必经方法
     */
    public Block newBlock(Long proof, String previous_hash) throws Exception {
//        if(proof==null) proof = proofOfWork(Long.parseLong(lastBlock().get("proof") + ""));
        Block block = new Block();
        // 设置区块类型
        int size = getChain().size();
        block.setIndex(size);
        block.setTimestamp(System.currentTimeMillis());
        block.setCarbonInfo(currentCarbon);
        block.setNode_key(Encrypt.encrypt(String.valueOf(block.getIndex()), KEY));
        block.setPrevious_hash(previous_hash != null ? previous_hash : getLastBlockHash());
        // 如果没有传递上一个区块的hash就计算出区块链中最后一个区块的hash
        getChain().add(block);
        if (size != 0) currentCarbon.setBlockType(CarbonTools.recognize_block_type(size));
        currentCarbon = null;
        return block;
    }

    /**
     * 创造新的CarbonBlock交易
     */
    public void newCarbonBlock(Carbon carbon) throws Exception {
        currentCarbon = carbon;
        // 一个交易节点一个区块
        newBlock(null, null);
    }

    /**
     * 生成区块的 SHA-256格式的 hash值
     *
     * @param block 区块
     * @return 返回该区块的hash
     */
    public static Object hash(Block block) {
        return new Encrypt().getSHA256(JSON.toJSONString(block));
    }

    /**
     * 简单的工作量证明:
     * - 查找一个 p' 使得 hash(pp') 以4个0开头
     * - p 是上一个块的证明, p' 是当前的证明
     * 无需挖矿所以作废
     *
     * @param last_proof 上一个块的证明
     * @return
     */
    public Long proofOfWork(Long last_proof) {
        Long proof = 0L;
        while (!validProof(last_proof, proof)) {
            proof += 1;
        }
        return proof;
    }

    /**
     * 验证证明: 是否hash(last_proof, proof)以4个0开头?
     *
     * @param last_proof 上一个块的证明
     * @param proof      当前的证明
     * @return 以4个0开头返回true，否则返回false
     */
    public boolean validProof(Long last_proof, Long proof) {
        String guess = last_proof + "" + proof;
        //使用SHA256
        String guess_hash = new Encrypt().getSHA256(guess);
        return guess_hash.startsWith(difficulty);
    }


    /**
     * @return boolean
     * @Description 验证链是否有效
     * @Param [chain]
     **/
    public boolean validChain(List<Block> chain) {
        Block lastBlock = chain.get(0);
        int currentIndex = 1;
        while (currentIndex < chain.size()) {
            Block block = chain.get(currentIndex);
            System.out.println(lastBlock.toString());
            System.out.println(block.toString());
            System.out.println("\n-------------------------\n");
            // 检查block的hash是否正确
            if (!block.getPrevious_hash().equals(hash(lastBlock))) {
                return false;
            }
            lastBlock = block;
            currentIndex++;
        }
        return true;
    }
}

